package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/lex"
	"gitcode.com/deyiyangyang/bampoo/par"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

// TestSetIntegration tests the full set! expression integration with lexer and parser
func TestSetIntegration(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Test parsing and evaluating a complete set! expression
	input := `(define x 10) (set! x 20) x`
	
	lexer := lex.New(input)
	parser := par.New(lexer)
	program := parser.ParseProgram()

	if len(parser.Errors()) > 0 {
		t.Fatalf("parser errors: %v", parser.Errors())
	}

	result, err := evaluator.Eval(program)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 20 {
		t.Errorf("expected final value to be 20, got %d", intVal.Value)
	}
}

// TestSetIntegrationString tests set! with string values
func TestSetIntegrationString(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Test parsing and evaluating a complete set! expression with strings
	input := `(define message "hello") (set! message "world") message`
	
	lexer := lex.New(input)
	parser := par.New(lexer)
	program := parser.ParseProgram()

	if len(parser.Errors()) > 0 {
		t.Fatalf("parser errors: %v", parser.Errors())
	}

	result, err := evaluator.Eval(program)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	strVal, ok := result.(*obj.StringValue)
	if !ok {
		t.Fatalf("expected StringValue, got %T", result)
	}

	if strVal.Value != "world" {
		t.Errorf("expected final value to be 'world', got '%s'", strVal.Value)
	}
}

// TestSetIntegrationWithArithmetic tests set! with arithmetic expressions
func TestSetIntegrationWithArithmetic(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Test parsing and evaluating a complete set! expression with arithmetic
	input := `(define result 0) (set! result (+ 5 3 2)) result`
	
	lexer := lex.New(input)
	parser := par.New(lexer)
	program := parser.ParseProgram()

	if len(parser.Errors()) > 0 {
		t.Fatalf("parser errors: %v", parser.Errors())
	}

	result, err := evaluator.Eval(program)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 10 { // 5 + 3 + 2 = 10
		t.Errorf("expected final value to be 10, got %d", intVal.Value)
	}
}

// TestSetIntegrationErrorUndefined tests error when setting undefined variable
func TestSetIntegrationErrorUndefined(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Test parsing and evaluating set! on undefined variable
	input := `(set! undefined 42)`
	
	lexer := lex.New(input)
	parser := par.New(lexer)
	program := parser.ParseProgram()

	if len(parser.Errors()) > 0 {
		t.Fatalf("parser errors: %v", parser.Errors())
	}

	_, err := evaluator.Eval(program)
	if err == nil {
		t.Fatal("expected error for setting undefined variable")
	}

	if !containsSubstring(err.Error(), "cannot set! undefined variable: undefined") {
		t.Errorf("expected error message to contain 'cannot set! undefined variable: undefined', got '%s'", err.Error())
	}
}

// TestSetIntegrationInBegin tests set! inside begin block
func TestSetIntegrationInBegin(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Test parsing and evaluating set! inside begin block
	input := `(define x 5) (begin (set! x 15) (set! x 25) x)`
	
	lexer := lex.New(input)
	parser := par.New(lexer)
	program := parser.ParseProgram()

	if len(parser.Errors()) > 0 {
		t.Fatalf("parser errors: %v", parser.Errors())
	}

	result, err := evaluator.Eval(program)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	intVal, ok := result.(*obj.IntegerValue)
	if !ok {
		t.Fatalf("expected IntegerValue, got %T", result)
	}

	if intVal.Value != 25 {
		t.Errorf("expected final value to be 25, got %d", intVal.Value)
	}
}